/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_transform.cpp - math transforming utilities
//


#include "r_local.h"


/*
 ==================
 R_LocalPointToWorld
 ==================
*/
void R_LocalPointToWorld (const glqVec3 &in, glqVec3 &out, const glqVec3 &origin, const glqMat3 &axis){

	glqVec3	tmp;

	tmp[0] = in[0] * axis[0][0] + in[1] * axis[1][0] + in[2] * axis[2][0];
	tmp[1] = in[0] * axis[0][1] + in[1] * axis[1][1] + in[2] * axis[2][1];
	tmp[2] = in[0] * axis[0][2] + in[1] * axis[1][2] + in[2] * axis[2][2];

	out[0] = tmp[0] + origin[0];
	out[1] = tmp[1] + origin[1];
	out[2] = tmp[2] + origin[2];
}

/*
 ==================
 R_WorldPointToLocal
 ==================
*/
void R_WorldPointToLocal (const glqVec3 &in, glqVec3 &out, const glqVec3 &origin, const glqMat3 &axis){

	glqVec3	tmp;

	tmp[0] = in[0] - origin[0];
	tmp[1] = in[1] - origin[1];
	tmp[2] = in[2] - origin[2];

	out[0] = tmp[0] * axis[0][0] + tmp[1] * axis[0][1] + tmp[2] * axis[0][2];
	out[1] = tmp[0] * axis[1][0] + tmp[1] * axis[1][1] + tmp[2] * axis[1][2];
	out[2] = tmp[0] * axis[2][0] + tmp[1] * axis[2][1] + tmp[2] * axis[2][2];
}

/*
 ==================
 R_WorldVectorToLocal
 ==================
*/
void R_WorldVectorToLocal (const glqVec3 &in, glqVec3 &out, const glqMat3 &axis){

	out[0] = in[0] * axis[0][0] + in[1] * axis[0][1] + in[2] * axis[0][2];
	out[1] = in[0] * axis[1][0] + in[1] * axis[1][1] + in[2] * axis[1][2];
	out[2] = in[0] * axis[2][0] + in[1] * axis[2][1] + in[2] * axis[2][2];
}

/*
 ==================
 R_WorldAxisToLocal
 ==================
*/
void R_WorldAxisToLocal (const glqMat3 &in, glqMat3 &out, const glqMat3 &axis){

	out[0][0] = in[0][0] * axis[0][0] + in[0][1] * axis[0][1] + in[0][2] * axis[0][2];
	out[0][1] = in[0][0] * axis[1][0] + in[0][1] * axis[1][1] + in[0][2] * axis[1][2];
	out[0][2] = in[0][0] * axis[2][0] + in[0][1] * axis[2][1] + in[0][2] * axis[2][2];
	out[1][0] = in[1][0] * axis[0][0] + in[1][1] * axis[0][1] + in[1][2] * axis[0][2];
	out[1][1] = in[1][0] * axis[1][0] + in[1][1] * axis[1][1] + in[1][2] * axis[1][2];
	out[1][2] = in[1][0] * axis[2][0] + in[1][1] * axis[2][1] + in[1][2] * axis[2][2];
	out[2][0] = in[2][0] * axis[0][0] + in[2][1] * axis[0][1] + in[2][2] * axis[0][2];
	out[2][1] = in[2][0] * axis[1][0] + in[2][1] * axis[1][1] + in[2][2] * axis[1][2];
	out[2][2] = in[2][0] * axis[2][0] + in[2][1] * axis[2][1] + in[2][2] * axis[2][2];
}


// ============================================================================


/*
 ==================
 R_TransformWorldToDevice
 ==================
*/
void R_TransformWorldToDevice (const glqVec3 &world, glqVec3 &ndc, const glqMat4 &modelviewProjectionMatrix){

	glqVec4	clip;
	float	scale;

	clip[0] = world[0] * modelviewProjectionMatrix[0][0] + world[1] * modelviewProjectionMatrix[1][0] + world[2] * modelviewProjectionMatrix[2][0] + modelviewProjectionMatrix[3][0];
	clip[1] = world[0] * modelviewProjectionMatrix[0][1] + world[1] * modelviewProjectionMatrix[1][1] + world[2] * modelviewProjectionMatrix[2][1] + modelviewProjectionMatrix[3][1];
	clip[2] = world[0] * modelviewProjectionMatrix[0][2] + world[1] * modelviewProjectionMatrix[1][2] + world[2] * modelviewProjectionMatrix[2][2] + modelviewProjectionMatrix[3][2];
	clip[3] = world[0] * modelviewProjectionMatrix[0][3] + world[1] * modelviewProjectionMatrix[1][3] + world[2] * modelviewProjectionMatrix[2][3] + modelviewProjectionMatrix[3][3];

	scale = 1.0f / clip[3];

	ndc[0] = clip[0] * scale;
	ndc[1] = clip[1] * scale;
	ndc[2] = clip[2] * scale;
}

/*
 ==================
 R_TransformDeviceToScreen
 ==================
*/
void R_TransformDeviceToScreen (const glqVec3 &ndc, glqVec3 &screen, const glqRect &viewport){

	screen[0] = (ndc[0] * 0.5f + 0.5f) * (viewport[2] - viewport[0]) + viewport[0];
	screen[1] = (ndc[1] * 0.5f + 0.5f) * (viewport[3] - viewport[1]) + viewport[1];
	screen[2] = (ndc[2] * 0.5f + 0.5f);
}


/*
 ==============================================================================

 COORDINATE SCALING AND ASPECT RATIO CORRECTION

 ==============================================================================
*/


/*
 ==================
 R_AdjustHorzCoords
 ==================
*/
void R_AdjustHorzCoords (horzAdjust_t adjust, float percent, float xIn, float wIn, float *xOut, float *wOut){

	renderCrop_t	*renderCrop;

	if (adjust == H_NONE){
		*xOut = xIn * r_screenFraction->floatValue;
		*wOut = wIn * r_screenFraction->floatValue;

		return;
	}

	renderCrop = &rg.renderCrops[rg.currentRenderCrop];

	if (renderCrop->aspectRatio != ASPECT_WIDE){
		*xOut = xIn * renderCrop->xScale;
		*wOut = wIn * renderCrop->xScale;

		return;
	}

	switch (adjust){
	case H_SCALE:
		*xOut = xIn * renderCrop->xScale;
		*wOut = wIn * renderCrop->xScale;

		break;
	case H_ALIGN_LEFT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_ALIGN_CENTER:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_ALIGN_RIGHT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent);
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_STRETCH_LEFT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	case H_STRETCH_WIDTH:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f;

		break;
	case H_STRETCH_RIGHT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	}
}

/*
 ==================
 R_AdjustVertCoords
 ==================
*/
void R_AdjustVertCoords (vertAdjust_t adjust, float percent, float yIn, float hIn, float *yOut, float *hOut){

	renderCrop_t	*renderCrop;

	if (adjust == V_NONE){
		*yOut = yIn * r_screenFraction->floatValue;
		*hOut = hIn * r_screenFraction->floatValue;

		return;
	}

	renderCrop = &rg.renderCrops[rg.currentRenderCrop];

	if (renderCrop->aspectRatio != ASPECT_HIGH){
		*yOut = yIn * renderCrop->yScale;
		*hOut = hIn * renderCrop->yScale;

		return;
	}

	switch (adjust){
	case V_SCALE:
		*yOut = yIn * renderCrop->yScale;
		*hOut = hIn * renderCrop->yScale;

		break;
	case V_ALIGN_TOP:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_ALIGN_CENTER:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_ALIGN_BOTTOM:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent);
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_STRETCH_TOP:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	case V_STRETCH_HEIGHT:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f;

		break;
	case V_STRETCH_BOTTOM:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	}
}

/*
 ==================
 R_AdjustHorzCoordsInt
 ==================
*/
void R_AdjustHorzCoordsInt (horzAdjust_t adjust, float percent, int xIn, int wIn, int *xOut, int *wOut){

	renderCrop_t	*renderCrop;

	if (adjust == H_NONE){
		*xOut = glqMath::FloatToInt(xIn * r_screenFraction->floatValue);
		*wOut = glqMath::FloatToInt(wIn * r_screenFraction->floatValue);

		return;
	}

	renderCrop = &rg.renderCrops[rg.currentRenderCrop];

	if (renderCrop->aspectRatio != ASPECT_WIDE){
		*xOut = glqMath::FloatToInt(xIn * renderCrop->xScale);
		*wOut = glqMath::FloatToInt(wIn * renderCrop->xScale);

		return;
	}

	switch (adjust){
	case H_SCALE:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->xScale);
		*wOut = glqMath::FloatToInt(wIn * renderCrop->xScale);

		break;
	case H_ALIGN_LEFT:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale);

		break;
	case H_ALIGN_CENTER:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias);
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale);

		break;
	case H_ALIGN_RIGHT:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent));
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale);

		break;
	case H_STRETCH_LEFT:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent);

		break;
	case H_STRETCH_WIDTH:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f);

		break;
	case H_STRETCH_RIGHT:
		*xOut = glqMath::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias);
		*wOut = glqMath::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent);

		break;
	}
}

/*
 ==================
 R_AdjustVertCoordsInt
 ==================
*/
void R_AdjustVertCoordsInt (vertAdjust_t adjust, float percent, int yIn, int hIn, int *yOut, int *hOut){

	renderCrop_t	*renderCrop;

	if (adjust == V_NONE){
		*yOut = glqMath::FloatToInt(yIn * r_screenFraction->floatValue);
		*hOut = glqMath::FloatToInt(hIn * r_screenFraction->floatValue);

		return;
	}

	renderCrop = &rg.renderCrops[rg.currentRenderCrop];

	if (renderCrop->aspectRatio != ASPECT_HIGH){
		*yOut = glqMath::FloatToInt(yIn * renderCrop->yScale);
		*hOut = glqMath::FloatToInt(hIn * renderCrop->yScale);

		return;
	}

	switch (adjust){
	case V_SCALE:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->yScale);
		*hOut = glqMath::FloatToInt(hIn * renderCrop->yScale);

		break;
	case V_ALIGN_TOP:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale);

		break;
	case V_ALIGN_CENTER:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias);
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale);

		break;
	case V_ALIGN_BOTTOM:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent));
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale);

		break;
	case V_STRETCH_TOP:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent);

		break;
	case V_STRETCH_HEIGHT:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent));
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f);

		break;
	case V_STRETCH_BOTTOM:
		*yOut = glqMath::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias);
		*hOut = glqMath::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent);

		break;
	}
}